var gamejs = require('gamejs');
var utils = require('js/utils');

/**
 * GameEntity is an extension of gamejs.Sprite that integrates features
 * previously duplicated across Turret, Enemy, Radar ...
 *
 * These features are:
 *     - Match management
 *     - Levels and upgrade managment
 *     - Center, rect, image and coordinates managment
 *
 * @param {Match} match, the Match were this GameEntity lives
 * @param {Array} center, the GameEntity location on the Match Map, in
 *                [x,y] format
 * @param {Array} upgradeList, the upgradeList used for this GameEntity
 *                (see GameEntity.UPGRADE_LIST for more information)
 * @param {Boolean} fillsMap, True if the GameEntity fills the space on the Map,
 *                  blocking other entities. The clearing/filling of points
 *                  it's done directly by the Map objects checking this field
 *                  with GameEntity.fillsMap() method.
 *                  [default false]
 * @param {Number} startingLvl, the level used to spawn the GameEntity
 *                [default 0]
 * @param {String} Shape, a string choosed between SHAPE_RECTANGULAR and
 *                 SHAPE_CIRCULAR. It's used for the collision between other
 *                 GameEntities [default SHAPE_CIRCULAR]
 * @param {Boolean} saleable, True if the GameEntity can be sold, false
 *                  otherwise [default true]
 *
 * @throws {RangeError} When startingLvl is < 0 or bigger than the maximum
 *                      reachable level specified in the upgradeList
 * @throws {Error} When used with an invalid Shape
 */
var GameEntity = function(match, center, upgradeList, fillsMap, startingLvl,
		shape, saleable ) {
	// Call gamejs.Sprite constructor
	GameEntity.superConstructor.apply(this);
	// Set all the private internal variables
	this._match = match;
	this._fillsMap = fillsMap || false;
	this._upgradeList = upgradeList;
	this._level = startingLvl || 0;
	// This check with undefined is here because the default value
	// for saleable is true
	if (saleable === undefined || saleable) {
		this._saleable = true;
	}
	else {
		this._saleable = false;
	}
	// Check if the level is acceptable
	if ( ! utils.validIndex(this._level, this._upgradeList)) {
		throw new RangeError('GameEntity: invalid starting level!');
	}
	// Set and check Shape
	this._shape = shape || SHAPE_CIRCULAR;
	if ( ! (this._shape === SHAPE_CIRCULAR ||
			this._shape === SHAPE_RECTANGULAR) ) {
		throw new Error("GameEntity: invalid Shape");
	}
	// Set this.image and this.rect needed by gamejs.Sprite
	this.updateImage();
	this.createRect(center);
	// Set the radius for circular gameEntities
	if (this._shape === SHAPE_CIRCULAR) {
		this.radius = Math.round(this.image.getSize()[0] / 2);
	}

};

// GameEntity extends gamejs.Sprite
gamejs.utils.objects.extend(GameEntity, gamejs.sprite.Sprite);

// Export GameEntity
exports.GameEntity = GameEntity;

/**
 * Extend GameEntity with the passed subClass.
 *
 * This static method puts the GameEntity prototype in subClass' prototype
 * chain and also copies the static method GameEntity.getBuyPrice
 * to the subClass.
 *
 * @param {Object} subClass
 */
GameEntity.extend = function(subClass) {
	// Put GameEntity prototype in subClass prototype chain
	gamejs.utils.objects.extend(subClass, GameEntity);
	// Copy the static method GameEntity.getBuyPrice(level, upgradeList)
	// to the subClass
	subClass.getBuyPrice = GameEntity.getBuyPrice;
};

// ========= MATCH MANAGMENT =========
/**
 * Returns the match where the GameEntity lives
 *
 * @param {Match}
 */
GameEntity.prototype.getMatch = function() {
	return this._match;
};

/**
 * Returns the Map used by the Match where the GameEntity lives
 *
 * @param {Map}
 */
GameEntity.prototype.getMatchMap = function() {
	return this._match.getMap();
};

// ========= COORDINATES MANAGMENT =========
/**
 * Returns the gameEntity shape
 *
 * @returns {String} "rectangular" or "circular"
 */
GameEntity.prototype.getShape = function() {
	return this._shape;
};

/**
 * Returns the center coordinates
 *
 * @returns {Array} center's coordinates using [x,y] format
 */
GameEntity.prototype.getCenter = function() {
	return this.rect.center;
};

/**
 * Only for GameEntity with circular shape returns the radius of the Image
 *
 * @returns {Number}
 *
 * @throws {Error} When called on a rectangular GameEntity
 */
GameEntity.prototype.getObstructiveRadius = function() {
	if ( this._shape === SHAPE_RECTANGULAR ) {
		throw new Error("GameEntity: you can't call getObstructiveRadius " +
			"on a rectangular GameEntity."
		);
	}
	else {
		return this.radius;
	}
};

/**
 * Returns the rect of this GameEnemy
 *
 * @returns {gamejs.Rect}
 */
GameEntity.prototype.getRect = function() {
	return this.rect;
};

/**
 * Checks if the GameEntity fills Map space
 *
 * @returns {Boolean} true if GameEntity fills Map space, false otherwise
 */
GameEntity.prototype.fillsMap = function() {
	return this._fillsMap;
};

// ========= LEVEL MANAGMENT =========
/**
 * An upgradeList must be an Array of objects, each object containing the
 * settings for a level, ordered from the first to the last:
 *    - upgradeList[0] it's the object with the setting for the first level
 *    - upgradeList[length-1] it's the object with the settings for the last
 *      level (the maximum reachable level by the GameEntity)
 *
 * Each object MUST have the 'image' property with the relative (to the
 * html page running the game) url to the image used for the GameEntity.
 * Relative urls must be used for a bug (or maybe stupid inteded feature)
 * of gamejs.image.load() method; absolute urls aren't loaded correctly.
 *
 * NOTE ON USING DIFFERENT IMAGES ON DIFFERENT LEVELS:
 * If you are using a GameEntity with fillsMap = false there is no problem.
 * If you are using a GameEntity with fillsMap = true things are more
 * complicated:
 *    - if you want to use the the upgrade method make sure that images
 *      across levels have the same size and shape. Wierd things can happen
 *      when upgrading a GameEntity to a bigger image.
 *  or
 *    - if you want levels with images of different size don't use the
 *      upgrade method. Instantiate directly the GameEntity to the desired
 *      level.
 *
 * Another two optional properties are:
 *    - 'price': used to store the number of credit to buy the upgrade
 *       [default 0]
 *    - 'resaleValue': used to store the number of credit that the player
 *       can make by selling the GameEntiy [default 0]
 *
 * When extending the GameEntity you can obviously add all the properties
 * that you need.
 *
 * To query a particular property you can use the getLevelSettings() to get
 * the current level object and then use the stantard js property access
 * syntax on the returned object. For example:
 *     exampleEntity.getLevelSettings().propertyName
 * or equivalently:
 *     exampleEntity.getLevelSettings()['propertyName']
 */
GameEntity.UPGRADE_LIST = [
	// Level 0
	{image: '/url/to/GameEntity0.png', price: 100},
	// Level 1
	{image: '/url/to/GameEntity1.png', price: 200},
	// Level 2, here imageShape uses the default value 'rectangular'
	{image: '/url/to/GameEntity2.png', userDefinedProperty: 'whatever'}
];

/**
 * Return the current level
 *
 * @return {Number}
 */
GameEntity.prototype.getLevel= function() {
	return this._level;
};

/**
 * Returns true if the GameEntity is at the maximum allowed level, false
 * otherwise
 *
 * @returns {Boolean}
 */
GameEntity.prototype.isAtMaximumLevel = function() {
	if (this.getLevel() === this._upgradeList.length - 1) {
		return true;
	}
	else {
		return false;
	}
};

/**
 * Returns true if the GameEntity can be upgraded (there's another available
 * level and enough credits), false otherwise
 *
 * @returns {Boolean}
 */
GameEntity.prototype.canUpgrade = function() {
	var credits = this.getMatch().getCredit();
	if ( ! this.isAtMaximumLevel() && this.getUpgradePrice() <= credits) {
		return true;
	}
	else {
		return false;
	}
};

/**
 * Return the upgradeList
 *
 * @return {Array}
 */
GameEntity.prototype.getUpgradeList= function() {
	return this._upgradeList;
};

/**
 * Returns the Object in the upgradeList containing the settings for
 * a level.
 *
 * Note: levels are zero indexed (0 is the first level, 1 is second one, ...)
 *
 * @param {Number} level [default current level]
 *
 * @returns {Object}
 *
 * @throws {RangeError} If level it's not a valid level
 */
GameEntity.prototype.getLevelSettings = function(level) {
	level = level || this._level;
	// Check if the argument is valid
	var maximumLevel = this._upgradeList.length - 1;
	if (typeof level !== 'number' || level < 0 || level > maximumLevel){
		throw new RangeError("Invalid level");
	}
	return this._upgradeList[level];
};

/**
 * Return the number of credit needed to upgrade the GameEntity.
 *
 * This uses the property 'price' inside the upgradeList.
 *
 * @return {Number}
 *
 * @throws {RangeError} If the GameEntity is already at the maximum level
 */
GameEntity.prototype.getUpgradePrice= function() {
	if ( this.isAtMaximumLevel() ) {
		throw new Error('GameEntity: Already at the maximum level');
	}
	var nextLevel = this._level + 1;
	// if no price is specified the upgrade is free
	var price = this.getLevelSettings(nextLevel).price || 0;
	return price;
};

/**
 * Returns the price to buy a GameEntity at the given level.
 * This calculate the sum of all 'price' property in the upgradeList for
 * levels < level.
 *
 * This is a static method that can be called without creating a GameEntity
 * object.
 *
 * NOTE: This method is not defined on the prorotype object so, when extending
 *      GameEntity, you have to manually copy it to the new Object or use the
 *      GameEntity.extend() method.
 *
 * @param {Array} upgradeList
 * @param {Number} level
 */
GameEntity.getBuyPrice = function(upgradeList, level) {
	// Default level is 0
	level = level || 0;
	// Check if level is valid
	if ( level < 0 || level > upgradeList.length - 1 ) {
		throw new Error("GameEntity.getBuyPrice: Invalid level");
	}
	// Calculate the Buy Price
	var price = 0;
	for (var i = 0; i <= level; i++ ) {
		price += upgradeList[i].price || 0;
	}
	return price;
};

/**
 * Upgrades the GameEntity by one level
 *
 * @throws {Error} If the GameEntity is already at the maximum level.
 * @throws {Error} If there isn't enough credits for the upgrade
 * @throws {Error} If the GameEntity cannot be upgrade for space problems:
 *                 this can happen when the upgraded version is bigger
 *                 than the "to be upgraded" one and there isn't enough space
 *                 around it to increase it's size.
 */
GameEntity.prototype.upgrade = function() {
	// Check if the maximum level is already reached
	if ( this.isAtMaximumLevel() ) {
		throw new Error('GameEntity: Already at the maximum level');
	}
	
	var price = this.getUpgradePrice();
	// Check that the user has enough money
	if ( ! this.getMatch().hasCredit(price)) {
		throw new Error('GameEntity: Not enough credits for the upgrade');
	}
	
	// pay the upgrade
	this.getMatch().changeCredit( - price);
	
	// Get the new and old image
	var oldImage = this.getLevelSettings().image;
	var newImage = this.getLevelSettings(this._level + 1 ).image;
	// Update the level
	this._level ++;
	// If the new image is different from the old one we must update
	// this.image
	if (oldImage !== newImage) {
		this.updateImage();
		this.createRect(this.rect.center);
	}
	return;
};

/**
 * Tells if the GameEntity is saleable.
 *
 * @return {Boolean}
 */
GameEntity.prototype.isSaleable = function() {
	return this._saleable;
};

/**
 * Return the resaleValue of the GameEntity (0 if resaleValue is undefined)
 *
 * This uses the property 'resaleValue' inside the upgradeList.
 *
 * @return {Number}
 *
 */
GameEntity.prototype.getResaleValue= function() {
	return this.getLevelSettings().resaleValue || 0;
};

/**
 * Sell the GameEntity, refounding the Match.
 *
 * @throws {Error} When selling a GameEntity that cannot be sold.
 */
GameEntity.prototype.sell = function() {
	if ( ! this.isSaleable() ) {
		throw new Error("GameEntity: you can't sell this GameEntity");
	}
	// Refound the match
	this.getMatchMap().removeGameEntity(this);
};

/**
 * Set this.image by loading the correct one from the upgradeList
 */
GameEntity.prototype.updateImage = function() {
	this.image = gamejs.image.load(this.getLevelSettings().image);
};

/**
 * Set this.rect using the center coordinates and the size of this.image.
 * Must be called after defining this.image
 *
 * @param {Array} center, the GameEntity location on the Match Map, in
 *                [x,y] format
 */
GameEntity.prototype.createRect = function(center) {
	var dims = this.image.getSize();
	var topLeftCornerX = Math.ceil(center[0] - (dims[0] / 2));
	var topLeftCornerY = Math.ceil(center[1] - (dims[1] / 2));
	var topLeftCorner = [topLeftCornerX, topLeftCornerY];
	this.rect = new gamejs.Rect(topLeftCorner, dims);
};